package src

// 追单
import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/go-redis/redis/v8"
	"github.com/sirupsen/logrus"

	"gitee.com/haifengat/goctp"
	ctp "gitee.com/haifengat/goctp/lnx"
)

// AfterSingle 追单
type AfterSingle struct {
	rdb               *redis.Client   // redis 连接
	ctx               context.Context // redis 上下文
	orderStrategyName []string        // 要处理的策略序列,格式：order.straName

	TrdAPI *ctp.Trade // 交易接口
	q      *ctp.Quote

	instrumentTick sync.Map // 合约:行情

	firstOffset   int // 首次发单偏移priceTick数
	cancelSeconds int // n 秒后不成交则撤单
	reorderTimes  int // 重试次数
	reorderOffset int // 重发偏移，以对价为基准价

	tradeFront, quoteFront                                     string
	loginInfo, brokerID, investorID, password, appID, authCode string   // CTP信息
	chClose                                                    chan int // 退出for循环
}

// NewAfterSingle 追单实例
func NewAfterSingle() (*AfterSingle, error) {
	a := AfterSingle{}
	a.chClose = make(chan int)

	var tmp string
	if tmp = os.Getenv("strategyName"); tmp == "" {
		return nil, errors.New("未配置策略名,格式：name1,name2")
	}

	logrus.Info("待处理策略：", tmp)
	a.orderStrategyName = make([]string, 0)
	for _, name := range strings.Split(tmp, ",") {
		a.orderStrategyName = append(a.orderStrategyName, "order."+strings.Trim(name, " "))
	}

	var redisAddr = ""
	if redisAddr = os.Getenv("redisAddr"); redisAddr == "" {
		return nil, errors.New("未配置环境变量: redisAddr")
	}

	logrus.Info(redisAddr)
	a.rdb = redis.NewClient(&redis.Options{
		Addr:         redisAddr,
		Password:     "",  // no password set
		DB:           0,   // use default DB
		PoolSize:     100, // 连接池最大socket连接数，默认为4倍CPU数， 4 * runtime.NumCPU
		MinIdleConns: 10,  //在启动阶段创建指定数量的Idle连接，并长期维持idle状态的连接数不少于指定数量；
		//超时
		DialTimeout:  5 * time.Second, //连接建立超时时间，默认5秒。
		ReadTimeout:  3 * time.Second, //读超时，默认3秒， -1表示取消读超时
		WriteTimeout: 3 * time.Second, //写超时，默认等于读超时
		PoolTimeout:  3 * time.Second, //当所有连接都处在繁忙状态时，客户端等待可用连接的最大等待时长，默认为读超时+1秒
	})
	a.ctx = context.Background()
	if _, err := a.rdb.Ping(a.ctx).Result(); err != nil {
		return nil, errors.New("rds连接错误：" + err.Error())
	}

	// 交易前置
	if tmp = os.Getenv("tradeFront"); tmp == "" {
		return nil, errors.New("未配置环境变量：tradeFront")
	}
	a.tradeFront = tmp
	if !strings.HasPrefix(a.tradeFront, "tcp://") {
		a.tradeFront = "tcp://" + a.tradeFront
	}
	// 行情前置
	if tmp = os.Getenv("quoteFront"); tmp == "" {
		return nil, errors.New("quoteFront")
	}
	a.quoteFront = tmp
	if !strings.HasPrefix(a.quoteFront, "tcp://") {
		a.quoteFront = "tcp://" + a.quoteFront
	}

	// 登录信息
	if tmp = os.Getenv("loginInfo"); tmp == "" {
		return nil, errors.New("未配置环境变量：loginInfo")
	}
	a.loginInfo = tmp
	fs := strings.Split(a.loginInfo, "/")
	a.brokerID, a.investorID, a.password, a.appID, a.authCode = fs[0], fs[1], fs[2], fs[3], fs[4]

	// 追单配置
	a.firstOffset = 999 // 999表示没有配置
	if tmp = os.Getenv("normal"); tmp != "" {
		cfgs := strings.Split(tmp, ",")
		a.firstOffset, _ = strconv.Atoi(strings.Trim(cfgs[0], " "))
		a.cancelSeconds, _ = strconv.Atoi(strings.Trim(cfgs[1], " "))
		a.reorderOffset, _ = strconv.Atoi(strings.Trim(cfgs[2], " "))
		a.reorderTimes, _ = strconv.Atoi(strings.Trim(cfgs[3], " "))
	}

	a.TrdAPI = ctp.NewTrade()
	a.q = ctp.NewQuote()

	a.instrumentTick = sync.Map{}
	return &a, nil
}

// StartCTP 启动CTP
func (a *AfterSingle) StartCTP() error {
	ch := make(chan *goctp.RspInfoField)
	a.TrdAPI.RegOnFrontConnected(func() {
		a.TrdAPI.ReqLogin(a.investorID, a.password, a.brokerID, a.appID, a.authCode)
	})
	a.TrdAPI.RegOnFrontDisConnected(func(reason int) {
		logrus.Error("trade disconnected: ", reason)
	})
	a.TrdAPI.RegOnErrRtnOrder(func(field *goctp.OrderField, info *goctp.RspInfoField) {
		bs, _ := json.Marshal(info)
		logrus.Info("onErrOrder: ", string(bs))
	})
	a.TrdAPI.RegOnRtnOrder(func(field *goctp.OrderField) {
		bs, _ := json.Marshal(field)
		logrus.Info("onorder: ", string(bs))
	})
	a.TrdAPI.RegOnRtnTrade(func(field *goctp.TradeField) {
		bs, _ := json.Marshal(field)
		logrus.Info("ontrade: ", string(bs))
	})
	a.TrdAPI.RegOnRspUserLogin(func(loginField *goctp.RspUserLoginField, info *goctp.RspInfoField) {
		if info.ErrorID != 0 {
			if info.ErrorID == 7 { // 重连时：未初始化
				go func() {
					logrus.Info("未初始化。。。")
					time.Sleep(10 * time.Minute)
					a.TrdAPI.ReqLogin(a.investorID, a.password, a.brokerID, a.appID, a.authCode)
				}()
			} else {
				logrus.Error(info)
				ch <- info
			}
		} else {
			logrus.Info(info)
			logrus.Info("start quote " + a.quoteFront)
			a.q.ReqConnect(a.quoteFront)
		}
	})

	a.q.RegOnFrontConnected(func() {
		a.q.ReqLogin(a.investorID, a.password, a.brokerID)
	})
	a.q.RegOnRspUserLogin(func(loginField *goctp.RspUserLoginField, info *goctp.RspInfoField) {
		ch <- info
	})
	a.q.RegOnTick(a.onTick)

	logrus.Info("start trade " + a.tradeFront)
	a.TrdAPI.ReqConnect(a.tradeFront)

	// 登录
	select {
	case log := <-ch:
		logrus.Info(log)
		if log.ErrorID != 0 {
			return errors.New(log.ErrorMsg)
		}
	case <-time.After(10 * time.Second): // 超时
		return errors.New("超时")
	}
	return nil
}

// Close 程序退出，清理
func (a *AfterSingle) Close() {
	a.chClose <- 1
	// a.TrdAPI.Release()
	// a.q.Release()
}

func (a *AfterSingle) onTick(tick *goctp.TickField) {
	a.instrumentTick.Store(tick.InstrumentID, tick)
}

// SubOrder 订阅实时委托 order.strategyName
func (a *AfterSingle) SubOrder() {
	sub := a.rdb.PSubscribe(a.ctx, a.orderStrategyName...)
	// 测试用例
	// go func() {
	// 	time.Sleep(time.Second)
	// 	ord := Order{
	// 		Instrument: "rb2105",
	// 		ID:         123098,
	// 		Direction:  "Sell",
	// 		Offset:     "Close",
	// 		Price:      6200,
	// 		Volume:     1,
	// 	}
	// 	bs, _ := json.Marshal(ord)
	// 	a.rdb.Publish(a.ctx, a.orderStrategyName[0], string(bs))
	// }()
	go func() {
		defer sub.Close()
		chMsg := sub.Channel()
		var order = Order{}
		for {
			select {
			case <-a.chClose:
				logrus.Info("exit suborder")
			case msg := <-chMsg:
				if msg == nil { // sub关闭后会触发此事件，此时chMsg已关闭, msg==nil
					break
				}
				jsOrder := msg.Payload
				logrus.Info(jsOrder)
				if err := json.Unmarshal([]byte(jsOrder[:]), &order); err != nil {
					logrus.Error("解析收到的order订阅错误：", err)
				} else {
					// 1231此处异常
					if _, loaded := a.instrumentTick.Load(order.Instrument); !loaded {
						a.q.ReqSubscript(order.Instrument) // 订阅行情
					}
					// 执行order
					dire := goctp.DirectionBuy
					instField, ok := a.TrdAPI.Instruments.Load(order.Instrument)
					if !ok {
						logrus.Error("未取到合约：", err)
						break
					}
					if a.firstOffset != 999 { // 默认追单
						logrus.Info("normal 追单：", order)
						// 价格偏移
						priceOffset := float64(a.firstOffset) * float64(instField.(*goctp.InstrumentField).PriceTick)
						if order.Direction == "Sell" {
							dire = goctp.DirectionSell
							priceOffset = -priceOffset // sell 减少价格
						}
						if order.Offset == "Open" { // 开仓
							offset := goctp.OffsetFlagOpen
							orderKey := a.TrdAPI.ReqOrderInsert(order.Instrument, dire, offset, order.Price+float64(priceOffset), order.Volume)
							go a.RunOrder(orderKey)
						} else { // 平仓
							instField, exists := a.TrdAPI.Instruments.Load(order.Instrument)
							if !exists {
								logrus.Error(order.Instrument, " 合约不存在!")
								return
							}
							if instField.(*goctp.InstrumentField).ExchangeID == "SHFE" || instField.(*goctp.InstrumentField).ExchangeID == "INE" {
								var key string
								if dire == goctp.DirectionBuy {
									key = fmt.Sprintf("%s_%c_%c", order.Instrument, goctp.PosiDirectionShort, goctp.HedgeFlagSpeculation)
								} else {
									key = fmt.Sprintf("%s_%c_%c", order.Instrument, goctp.PosiDirectionLong, goctp.HedgeFlagSpeculation)
								}
								pf, exists := a.TrdAPI.Positions.Load(key)
								if !exists {
									logrus.Error(order.Instrument, " 无持仓")
									return
								}
								posiField := pf.(*goctp.PositionField)
								// 无今仓：全部平昨
								if posiField.TodayPosition == 0 {
									offset := goctp.OffsetFlagCloseYesterday
									logrus.Info(order.Instrument, dire, offset, order.Price+float64(priceOffset), order.Volume)
									orderKey := a.TrdAPI.ReqOrderInsert(order.Instrument, dire, offset, order.Price+float64(priceOffset), order.Volume)
									go a.RunOrder(orderKey)
								} else if order.Volume <= posiField.TodayPosition { // 全部平今
									offset := goctp.OffsetFlagCloseToday
									logrus.Info(order.Instrument, dire, offset, order.Price+float64(priceOffset), order.Volume)
									orderKey := a.TrdAPI.ReqOrderInsert(order.Instrument, dire, offset, order.Price+float64(priceOffset), order.Volume)
									go a.RunOrder(orderKey)
								} else {
									// 平今
									offset := goctp.OffsetFlagCloseToday
									logrus.Info(order.Instrument, dire, offset, order.Price+float64(priceOffset), posiField.TodayPosition)
									orderKey := a.TrdAPI.ReqOrderInsert(order.Instrument, dire, offset, order.Price+float64(priceOffset), posiField.TodayPosition)
									go a.RunOrder(orderKey)
									// 平昨
									offset = goctp.OffsetFlagCloseYesterday
									yd := order.Volume - posiField.TodayPosition
									logrus.Info(order.Instrument, dire, offset, order.Price+float64(priceOffset), yd)
									orderKey = a.TrdAPI.ReqOrderInsert(order.Instrument, dire, offset, order.Price+float64(priceOffset), yd)
									go a.RunOrder(orderKey)
								}
							} else { // 平仓
								offset := goctp.OffsetFlagClose
								logrus.Info(order.Instrument, dire, offset, order.Price+float64(priceOffset), order.Volume)
								orderKey := a.TrdAPI.ReqOrderInsert(order.Instrument, dire, offset, order.Price+float64(priceOffset), order.Volume)
								go a.RunOrder(orderKey)
							}
						}
						// 策略所用算法 if straName := strings.TrimPrefix(a.orderStrategyName, "order."); straName in qount01
					}
				}
			}
		}
	}()
}

// RunOrder 追单
func (a *AfterSingle) RunOrder(orderKey string) {
	times := 0
	for {
		time.Sleep(time.Duration(a.cancelSeconds) * time.Second)
		// 取委托状态
		// a.TrdAPI.Orders.Range(func(key, value interface{}) bool {
		// 	fmt.Print([]byte(key.(string)))
		// 	return true
		// })
		if obj, ok := a.TrdAPI.Orders.Load(orderKey); !ok {
			logrus.Error("取委托状态错误：不存在", orderKey)
			break
		} else {
			order := obj.(*goctp.OrderField)
			// 已成交 或撤单
			if order.OrderStatus == goctp.OrderStatusAllTraded {
				logrus.Info("委托结束，状态：AllTraded")
				return
			}
			if order.OrderStatus == goctp.OrderStatusCanceled {
				logrus.Info("Canceled")
				return
			}
			// 追单: 即时行情
			obj, ok := a.instrumentTick.Load(order.InstrumentID)
			if !ok {
				logrus.Error("未收到此合约的行情：", order.InstrumentID)
				break
			} else {
				if o, ok := a.TrdAPI.Instruments.Load(order.InstrumentID); !ok {
					logrus.Error("合约列表中不存在合约：", order.InstrumentID)
					break
				} else {
					inst := o.(*goctp.InstrumentField)
					tick := obj.(*goctp.TickField)
					// 读取重发次数
					if times < a.reorderTimes {
						price := tick.AskPrice1 + float64(a.reorderOffset)*float64(inst.PriceTick)
						if order.Direction == goctp.DirectionSell {
							price = tick.BidPrice1 - float64(a.reorderOffset)*float64(inst.PriceTick)
						}

						times++
						logrus.Info("reorder ", orderKey, " times: ", times, " price:", price)
						orderKey = a.TrdAPI.ReqOrderInsert(order.InstrumentID, order.Direction, order.OffsetFlag, price, order.VolumeLeft)
					} else { // 市价单
						// 应用涨跌停价替代
						price := tick.UpperLimitPrice
						if order.Direction == goctp.DirectionSell {
							price = tick.LowerLimitPrice
						}
						logrus.Info("last reorder ", orderKey, " times: ", times, " price:", price)
						a.TrdAPI.ReqOrderInsert(order.InstrumentID, order.Direction, order.OffsetFlag, price, order.VolumeLeft)
						break
					}
				}
			}
		}
	}
}
